﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Net.NetworkInformation;
using Nojin.Deployment.UpdaterServer;
using System.Net;
using System.IO;
using System.Threading;
using System.Windows.Forms;

namespace Nojin.Deployment
{
    /// <summary>
    /// Updates the application.
    /// Shows default user interface for update progress.
    /// To have your custom user interface implement the same class as this class and show your own forms.
    /// </summary>
    /// <remarks>
    /// It class uses the <seealso cref="ApplicationDeployment"/> class to check for new update availability
    /// and if there is any update available gets the appropriate process object of type <seealso cref="ApplicationDeploymentProcess"/>.
    /// Having the process object handles it events and collaborates with it until the process completes the update or becomes canceled.
    /// </remarks>
    public static class AutoUpdate
    {
        #region Fields
        static ShowUpdateProgressForm upgradeProgressForm;
        static ApplicationDeployment deployment;
        static UpdateInfo updateInfo;
        static bool userCanceled;
        static int? autoUpdateInterval;
        static System.Windows.Forms.Timer timer;
        #endregion

        #region Constructors
        static AutoUpdate()
        {
            deployment = new ApplicationDeployment();
        }
        #endregion

        #region Properties

        /// <summary>
        /// The interval of auto update checks in miliseconds.
        /// It overrides the settings specified in configuration file.
        /// Set to null to read the value from configuration file.
        /// </summary>
        public static int? AutoUpdateInterval
        {
            get
            {
                return autoUpdateInterval;
            }
            set
            {
                autoUpdateInterval = value;
                if (autoUpdateInterval < 10)
                    throw new ArgumentException("The value is two small.");
            }
        }
        #endregion

        #region Methods

        // public:

        /// <summary>
        /// To satisfy the autoupdate scenario this method is intented to be called as soon as possible 
        /// after your application start-up, It performs a initial check for new update availiability 
        /// and during application execution checks at intervals
        /// </summary>
        /// <remarks>
        /// The interval of checks could be specified by configuration with key "autoUpdateCheckInterval".
        /// note that value should be specified in miliseconds.
        /// 
        /// <code>
        ///     <appSettings>
        ///         <add key="autoUpdateCheckInterval" value="7200" /> <!-- set 2hour as autoupdate interval time -->
        ///     </appSettings>
        /// </code>
        /// 
        /// 
        /// 
        /// 
        /// If you want to promote option which allows users to cancel auto update feature you should prevent calling
        /// this method.
        /// <code>
        ///     if ( options[AutomaticUpdate] )
        ///         AutoUpdate.OnApplicationStartUp(true);
        /// </code>
        /// 
        /// </remarks>
        /// <param name="activateGahGahCheck">Set true to activate checks performed in intervals.</param>
        public static void OnApplicationStartup(bool activateGahGahCheck)
        {

            bool connected = CheckAutoUpdateServerAvailability();

            if (connected)
                CheckAndInstallInternal();

            if (activateGahGahCheck)
            {
                if (timer == null)
                {
                    timer = new System.Windows.Forms.Timer();                    
                    
                    timer.Tick += new EventHandler(
                        delegate(object sender, EventArgs e)
                        {                            
                            CheckGahGah();
                        });
                }

                timer.Interval = GetConfigInterval();
                timer.Start();

                NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
            }
        }

        /// <summary>
        /// Calls the <seealso cref="OnApplicationStartup"/> with default value true for activateCheckGahGah parameter.
        /// </summary>
        public static void OnApplicationStartup()
        {
            OnApplicationStartup(true);
        }

        public static void CheckAndInstall()
        {
            Thread checkThread = new Thread(delegate()
            {
                CheckAndInstallInternal();
            });
            checkThread.Start();
        }

        /// <summary>
        /// Explicitly checks for a new update and if any one is available, starts the progress.
        /// </summary>
        /// <remarks>
        /// You may provide a command for your subject application to check update when user desire.
        /// But note if it is recommended to set a call to <seealso cref="OnApplicationStartUp"/>
        /// at your subject applications start-up. In that case check will be performed in intervals 
        /// during application lifecycle.
        /// </remarks>
        private static void CheckAndInstallInternal()
        {
            updateInfo = deployment.CheckForUpdate();
            if (updateInfo != null)
            {
                deployment.ProcessCreated += new EventHandler<ProcessCreatedEventArgs>(deployment_ProcessCreated);
                deployment.CreateProcessAsync(updateInfo);
            }
        }

        /// <summary>
        /// Stops interval checks.
        /// </summary>
        public static void StopGahGahChecks()
        {
            if (timer != null)
            {
                timer.Stop();
            }
        }

        /// <summary>
        /// Restart interval checks.
        /// </summary>
        public static void RestartGahGahChecks()
        {
            if (timer != null)
            {
                timer.Start();
            }
        }

        // private:

        static void CheckGahGah()
        {
            if (userCanceled) // If during current lifecycle of application user canceled the auto update
                return;

            bool connected = CheckAutoUpdateServerAvailability();

            if (!connected)
                return;

            CheckAndInstallInternal();
        }

        static bool CheckAutoUpdateServerAvailability()
        {
            string updateServiceUriText = ConfigurationManager.AppSettings[Constants.AutoUpdateServicUriCongigKey];
            Uri updateServiceUri = new Uri(updateServiceUriText);
            IPAddress[] addresses = Dns.GetHostAddresses(updateServiceUri.Host);
            foreach (IPAddress address in addresses)
            {
                if (Network.CheckSiteConnection(address))
                    return true;
            }
            return false;
        }

        static void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            if (e.IsAvailable)
                CheckGahGah();
        }

        static int GetConfigInterval()
        {
            if (autoUpdateInterval != null) // If the configuration value overrided programmatically.
                return autoUpdateInterval.Value;

            string autoUpdateCheckIntervalConfig = ConfigurationManager.AppSettings[Constants.AutoUpdateCheckIntervalConfigKey];
            int result = 5 * 60 * 1000;

            bool configReaded = int.TryParse(autoUpdateCheckIntervalConfig, out result);

            if (!configReaded)
                result = 5 * 60 * 1000;
            else
                result = result * 1000;

            return result;
        }

        static void deployment_ProcessCreated(object sender, ProcessCreatedEventArgs e)
        {
            bool timerStoped = false;

            if (timer != null)
            {
                timer.Stop(); // To prevent from conflicts. During downloading new version, the check for new version should be disabled.
                timerStoped = true;
            }

            bool startDownload = true;
            bool startInstall = false;

            ApplicationDeploymentProcess process = e.Process; // Reterive the new created process object

            if (updateInfo.SilentUpdate) // If update is a silent update 
            {
                startDownload = true;    // without creating any user interface start the download
            }
            else
            {
                if (process.NewUpdate) // If this a update with no history saved in disk
                {
                    // Confirm the update progress for new version.
                    NewVersionMessageForm confirmForm = new NewVersionMessageForm(updateInfo);
                    confirmForm.ShowDialog();
                    if (confirmForm.DialogResult == DialogResult.OK)
                    {
                        process.SaveState(); // Save state, so in later the dialoug will ask user to continue.
                    }
                    else
                        startDownload = false; // prevent starting the progress
                }
                else // If this is previously started update progress...
                {
                    if (process.IsDownloadCompleted) // and if the download is completed(and install remained)
                    {
                        // dont download again, but start the installation of downloaded files
                        startInstall = true;
                        startDownload = false;
                    }
                    else
                    {
                        // Ask user to continue the download
                        ContinueDownloadMessageForm confirmForm = new ContinueDownloadMessageForm(updateInfo, process.GetTotalDownloadedSize());                        
                        confirmForm.ShowDialog();
                        if (confirmForm.DialogResult == DialogResult.OK)
                        {
                        }
                        else
                        {
                            startDownload = false; // prevent starting the progress
                        }
                    }
                }
            }

            if (startDownload)
            {
                // Create the progress form, assosiate the events which ties form to progress state.
                upgradeProgressForm = new ShowUpdateProgressForm(updateInfo);
                process.DownloadProgressReport += new EventHandler<ProgressReportEventArgs>(process_DownloadProgressReport);
                process.DownloadCompleted += new EventHandler(process_DownloadCompleted);
                process.InstallReady += new System.ComponentModel.CancelEventHandler(process_InstallReady);
                process.InstallCompleted += new EventHandler(process_InstallCompleted);
                process.Run();
                //upgradeProgressForm.Show();
                Application.Run(upgradeProgressForm); // Use a seperate application domain for AutoUpdate to decrease side effects on subject application

            }

            if (startInstall)
            {                
                process.InstallReady += new System.ComponentModel.CancelEventHandler(process_InstallReady);
                process.InstallCompleted += new EventHandler(process_InstallCompleted);
                process.Run();
            }

            if ( !startDownload && !startInstall )
            {
                if (timer != null && timerStoped)
                {
                    timer.Interval = GetConfigInterval();

                    timer.Start(); // If the user doesn't choiced to download the new version, we will show download confirm later.
                }
            }

        }

        static void process_DownloadProgressReport(object sender, ProgressReportEventArgs e)
        {
            userCanceled = upgradeProgressForm.UpdateInfo(e.CurrentFileName, e.CurrentFileNumber, e.AllFilesCount, e.BytesReceived, e.TotalBytesToReceive);                        
            e.Cancel = userCanceled;

            if (userCanceled)
            {
                if (timer != null)
                    timer.Start();
            }

        }

        static void process_DownloadCompleted(object sender, EventArgs e)
        {
            //MessageBox.Show("دانلود کامل شد.");
            //if (upgradeProgressForm != null)
            //    upgradeProgressForm.DownloadCompleted();
        }

        static void process_InstallReady(object sender, System.ComponentModel.CancelEventArgs e)
        {
            MessageBox.Show(Texts.NewVersionIsDownloadedAndIsReadyToInstall);
        }

        static void process_InstallCompleted(object sender, EventArgs e)
        {
            if (upgradeProgressForm != null)
                MessageBox.Show(Texts.InstallIsCompletedAndYouMustReopenProgram);


            if (upgradeProgressForm != null)
            {
                // It is important: close the updateProgressForm at final, because closing this form will cause terminating the AutoUpdate application domain.
                upgradeProgressForm.DownloadCompleted();
                upgradeProgressForm = null;
            }
        }

        #endregion
    }

}
